﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using CSharpExt.Collections;

namespace CSharpExt.Time
{
    public interface ITimedRule<T>
    {
        #region Interface

        bool IsValidDateTime(DateTime dt);

        T Value { get; }

        #endregion
    }

    // ----------------------------------------------------------------------

    abstract class TimedBaseRule<T> : ITimedRule<T>
    {
        #region Private Data

        protected T _value;

        #endregion

        #region Constructor

        internal TimedBaseRule(T value)
        {
            _value = value;
        }

        #endregion

        #region Properties

        public T Value { get { return _value; } }

        #endregion

        #region Operations

        public abstract bool IsValidDateTime(DateTime dt);

        #endregion

        #region Implementation

        #endregion
    }

    // ----------------------------------------------------------------------

    class TimedDowRule<T> : TimedBaseRule<T>
    {
        #region Private Data

        private static readonly String[] DOW_IT =
            {
                "lun",
                "mar",
                "mer",
                "gio",
                "ven",
                "sab",
                "dom"
            };

        private static readonly String[] DOW_EN =
            {
                "mon",
                "tue",
                "wed",
                "thr",
                "fri",
                "sat",
                "sun"
            };

        private bool _week;
        private DayOfWeek _dow;
        private DateTime _begin = new DateTime(1, 1, 1, 0, 0, 0);
        private DateTime _end = new DateTime(1, 1, 1, 23, 59, 59);
        private int _ibegin;
        private int _iend;

        #endregion

        #region Constructor

        public TimedDowRule(T value, String srule)
            : base(value)
        {
            parseRule(srule);
        }

        #endregion

        #region Operations

        public override bool IsValidDateTime(DateTime dt)
        {
            return (isValidDay(dt) && isValidTime(dt));
        }

        public override string ToString()
        {
            return String.Format("{0}:{1}/{2}-{3}", _value, _week ? "*" : _dow.ToString(), _begin.ToString("HH:mm:ss"),
                                 _end.ToString("HH:mm:ss"));
        }

        #endregion

        #region Implementation

        // -- Validity check 

        private bool isValidDay(DateTime dt)
        {
            if (_week)
                return true;
            else
                return _dow == dt.DayOfWeek;
        }

        private bool isValidTime(DateTime dt)
        {
            int idt = secondInDay(dt);
            return _ibegin <= idt && idt <= _iend;
        }

        private static int secondInDay(DateTime dt)
        {
            return (dt.Hour * 60 + dt.Minute) * 60 + dt.Second;
        }

        // -- Parse rule

        private void parseRule(String srule)
        {
            int sep;
            String svalue;
            String sdow;
            String sbegin;
            String send;

            // bool:...
            sep = srule.IndexOf(':');
            if (sep == -1)
            {
                svalue = srule;
                srule = String.Empty;
            }
            else
            {
                svalue = srule.Substring(0, sep);
                srule = srule.Substring(sep + 1);
            }

            // dow/...
            sep = srule.IndexOf('/');
            if (sep == -1)
            {
                sdow = srule;
                srule = String.Empty;
            }
            else
            {
                sdow = srule.Substring(0, sep);
                srule = srule.Substring(sep + 1);
            }

            // start
            // start-
            // -end
            // start-end
            sep = srule.IndexOf('-');
            if (sep == -1)
            {
                sbegin = srule;
                send = String.Empty;
            }
            else
            {
                sbegin = srule.Substring(0, sep);
                send = srule.Substring(sep + 1);
            }

            // parse dow/begin/end
            parseDow(sdow, srule);
            _begin = parseDateTime(sbegin);
            _end = parseDateTime(send);
        }

        private void parseDow(String sdow, String srule)
        {
            if (String.IsNullOrEmpty(sdow) || "*".Equals(sdow))
                _week = true;
            else if (sdow.StartsWith(DOW_IT[0]) || sdow.StartsWith(DOW_EN[0]))
                _dow = DayOfWeek.Monday;
            else if (sdow.StartsWith(DOW_IT[1]) || sdow.StartsWith(DOW_EN[1]))
                _dow = DayOfWeek.Tuesday;
            else if (sdow.StartsWith(DOW_IT[2]) || sdow.StartsWith(DOW_EN[2]))
                _dow = DayOfWeek.Wednesday;
            else if (sdow.StartsWith(DOW_IT[3]) || sdow.StartsWith(DOW_EN[3]))
                _dow = DayOfWeek.Thursday;
            else if (sdow.StartsWith(DOW_IT[4]) || sdow.StartsWith(DOW_EN[4]))
                _dow = DayOfWeek.Friday;
            else if (sdow.StartsWith(DOW_IT[5]) || sdow.StartsWith(DOW_EN[5]))
                _dow = DayOfWeek.Saturday;
            else if (sdow.StartsWith(DOW_IT[6]) || sdow.StartsWith(DOW_EN[6]))
                _dow = DayOfWeek.Sunday;
            else
                throw new ArgumentException(String.Format("Unable to parse rule '{0}'", srule));
        }

        private DateTime parseDateTime(String sdt)
        {
            if (sdt.Length == 0)
                return new DateTime(1, 1, 1, 23, 59, 59);
            else
                return DateTime.ParseExact(sdt, "H:m:s", CultureInfo.InvariantCulture);
        }

        #endregion
    }

    // ----------------------------------------------------------------------

    class TimedCronRule<T> : TimedBaseRule<T>
    {
        #region Private Data

        private BitSet _seconds = new BitSet(60);
        private BitSet _minutes = new BitSet(60);
        private BitSet _hours = new BitSet(24);
        private BitSet _days = new BitSet(32);      // 1..31
        private BitSet _months = new BitSet(13);    // 1..12
        private BitSet _dows = new BitSet(7);       // 0.. 7 0->domenica?

        #endregion

        #region Constructor

        public TimedCronRule(T value, String srule)
            : base(value)
        {
            parseRule(srule);
        }

        #endregion

        #region Operations

        public override bool IsValidDateTime(DateTime dt)
        {
            return _seconds.Test(dt.Second)
                   && _minutes.Test(dt.Minute)
                   && _hours.Test(dt.Hour)
                   && _days.Test(dt.Day)
                   && _months.Test(dt.Month)
                   && _dows.Test((int)dt.DayOfWeek);
        }

        public override string ToString()
        {
            return String.Format("{0}:{1} {2} {3} {4} {5} {6}",
                                 _value,
                                 _seconds,
                                 _minutes,
                                 _hours,
                                 _days,
                                 _months,
                                 _dows);
        }

        #endregion

        #region Implementation

        // -- Parse rule

        private void parseRule(String srule)
        {
            String[] parts = srule.Split(' ');
            int length = parts.Length;

            if (length > 0) _seconds = BitSet.Parse(parts[0], 60);
            if (length > 1) _minutes = BitSet.Parse(parts[1], 60);
            if (length > 2) _hours = BitSet.Parse(parts[2], 24);
            if (length > 3) _days = BitSet.Parse(parts[3], 32, 1); else _days = BitSet.Parse("*", 32, 1);
            if (length > 4) _months = BitSet.Parse(parts[4], 13, 1); else _months = BitSet.Parse("*", 13, 1);
            if (length > 5) _dows = BitSet.Parse(parts[5], 7); else _dows = BitSet.Parse("*", 7);
        }

        #endregion
    }

    // ----------------------------------------------------------------------

    public class TimedValue<T>
    {
        #region Private Data

        private List<ITimedRule<T>> _rules = new List<ITimedRule<T>>();
        private T _default = default(T);

        #endregion

        #region Constructor

        public TimedValue()
        {
        }

        #endregion

        #region Properties

        public T Default
        {
            set { _default = value; }
            get { return _default; }
        }

        #endregion

        #region Operations

        public TimedValue<T> AddRule(String srule)
        {
            int pos = srule.IndexOf(':');
            String svalue = srule.Substring(0, pos);
            srule = srule.Substring(pos + 1);

            T value = parseValue(svalue);

            return AddRule(value, srule);
        }

        public TimedValue<T> AddRule(T value, String srule)
        {
            if (srule.IndexOf(' ') != -1)
                _rules.Add(new TimedCronRule<T>(value, srule));
            else
                _rules.Add(new TimedDowRule<T>(value, srule));
            return this;
        }

        // ------------------------------------------------------------------

        public T Value()
        {
            return Value(DateTime.Now);
        }

        public T Value(DateTime dt)
        {
            foreach (ITimedRule<T> trule in _rules)
            {
                if (trule.IsValidDateTime(dt))
                    return trule.Value;
            }
            return _default;
        }

        // ------------------------------------------------------------------

        public ITimedRule<T> SelectedRule(DateTime dt)
        {
            foreach (ITimedRule<T> trule in _rules)
            {
                if (trule.IsValidDateTime(dt))
                    return trule;
            }
            return null;
        }

        #endregion

        #region Implementation

        private static Type[] PARSE_PARAMS = new[] { typeof(String) };
        private static Type[] PARSE_FORMATTED_PARAMS = new[] { typeof(String), typeof(IFormatProvider) };

        protected virtual T parseValue(String svalue)
        {
            MethodInfo mParse;
            Type vtype = typeof(T);

            if (svalue == null)
            {
                return default(T);
            }
            if (vtype == typeof(float) || vtype == typeof(double))
            {
                mParse = vtype.GetMethod("Parse", PARSE_FORMATTED_PARAMS);
                return (T)mParse.Invoke(null, new Object[] { svalue, CultureInfo.InvariantCulture });
            }
            if (vtype == typeof(String))
            {
                return (T)(Object)svalue;
            }
            mParse = vtype.GetMethod("Parse", PARSE_PARAMS);
            if (mParse != null)
            {
                return (T)mParse.Invoke(null, new Object[] { svalue });
            }
            else
            {
                return (T)(Object)svalue;
            }
        }

        #endregion
    }
}
